package org.spiderflow.controller;

import cn.hutool.core.io.IoUtil;
import com.alibaba.ttl.TtlRunnable;
import com.aliyun.oss.common.utils.IOUtils;
import com.google.common.collect.Lists;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.NoSuchAlgorithmException;
import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spiderflow.concurrent.LinkedThreadSubmitStrategy;
import org.spiderflow.concurrent.SpiderFlowThreadPoolExecutor;
import org.spiderflow.concurrent.SpiderFlowThreadPoolExecutor.SubThreadPoolExecutor;
import org.spiderflow.concurrent.ThreadSubmitStrategy;
import org.spiderflow.core.service.SpArticleService;
import org.spiderflow.core.utils.FileUtils;
import org.spiderflow.executor.ShapeExecutor;
import org.spiderflow.mongodb.model.FileDocument;
import org.spiderflow.mongodb.model.ResponseModel;
import org.spiderflow.mongodb.service.IFileService;
import org.spiderflow.utils.MD5Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.core.CallableStatementCallback;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;

@CrossOrigin(origins = "*", maxAge = 3600)
@Controller
@RequestMapping("/mongodb")
public class MongoFileController {

  Logger logger = LoggerFactory.getLogger(MongoFileController.class);

  @Autowired
  private IFileService fileService;

  @Autowired
  private SpArticleService spArticleService;

  @Autowired
  CommonsMultipartResolver multipartResolver;

  @Autowired
  JdbcTemplate jdbcTemplate;

  @Value("${spider.downfile.bth.thread.max:8}")
  private Integer totalThreads;

  @Value("${spider.downfile.bth.thread.default:4}")
  private Integer defaultThreads;

  public static ThreadPoolExecutor executorInstance;

  @PostConstruct
  private void init() {
    executorInstance = new ThreadPoolExecutor(defaultThreads, totalThreads, 60, TimeUnit.SECONDS,
        new ArrayBlockingQueue<>(50), new ThreadFactory() {

      @Override
      public Thread newThread(Runnable r) {
        return new Thread(r, "文件并发下载并上传mongodb");
      }
    }, new RejectedExecutionHandler() {

      @Override
      public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {

      }
    });
  }

  @RequestMapping(value = "/index", method = RequestMethod.GET)
//  @ResponseBody
  public String index(String alias, Model model) {
    // 展示最新二十条数据
    model.addAttribute("files", fileService.listFilesByPage(alias, 0, 20));
    model.addAttribute("alias", alias);
    return "mongo";
  }

  /**
   * 上传文件列表
   */
  @RequestMapping(value = "/list", method = RequestMethod.GET)
  @ResponseBody
  public List<FileDocument> list(String alias, int pageIndex, int pageSize) {
    List<FileDocument> fileDocuments = fileService.listFilesByPage(alias, pageIndex, pageSize);
    logger.info("mongo数据总条数：{}", fileDocuments.size());
    return fileDocuments;
  }

  /**
   * 获取文件片信息
   */
  @RequestMapping(value = "/files/{id}", method = RequestMethod.GET)
  @ResponseBody
  public ResponseEntity<Object> serveFile(@RequestParam("alias") String alias,
      @PathVariable("id") String id)
      throws UnsupportedEncodingException {

    Optional<FileDocument> file = fileService.getFileById(alias, id);

    if (file.isPresent()) {
      return ResponseEntity.ok()
          .header(HttpHeaders.CONTENT_DISPOSITION,
              "attachment; fileName=" + new String(file.get().getName().getBytes("utf-8"),
                  "ISO-8859-1"))
          .header(HttpHeaders.CONTENT_TYPE, "application/octet-stream")
          .header(HttpHeaders.CONTENT_LENGTH, file.get().getSize() + "")
          .header("Connection", "close")
          .body(file.get().getContent());
    } else {
      return ResponseEntity.status(HttpStatus.NOT_FOUND).body("File was not fount");
    }

  }

  /**
   * 在线显示文件
   *
   * @param id 文件id
   */
  @RequestMapping(value = "/view/{id}", method = RequestMethod.GET)
  @ResponseBody
  public ResponseEntity<Object> serveFileOnline(@RequestParam("alias") String alias,
      @PathVariable("id") String id) {
    Optional<FileDocument> file = fileService.getFileById(alias, id);
    if (file.isPresent()) {
      return ResponseEntity.ok()
          .header(HttpHeaders.CONTENT_DISPOSITION, "fileName=" + file.get().getName())
          .header(HttpHeaders.CONTENT_TYPE, file.get().getContentType())
          .header(HttpHeaders.CONTENT_LENGTH, file.get().getSize() + "")
          .header("Connection", "close")
          .body(file.get().getContent());
    } else {
      return ResponseEntity.status(HttpStatus.NOT_FOUND).body("File was not found");
    }
  }

  /**
   * 下载文件
   */
  @RequestMapping(value = "/{id}", method = RequestMethod.GET)
  @ResponseBody
  public ResponseEntity<Object> downloadFileById(@RequestParam("alias") String alias,
      @PathVariable("id") String id)
      throws UnsupportedEncodingException {
    Optional<FileDocument> file = fileService.getFileById(alias, id);
    if (file.isPresent()) {
      return ResponseEntity.ok()
          .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; fileName=" + URLEncoder
              .encode(file.get().getName(), "utf-8"))
          .header(HttpHeaders.CONTENT_TYPE, "application/octet-stream")
          .header(HttpHeaders.CONTENT_LENGTH, file.get().getSize() + "")
          .header("Connection", "close")
          .body(file.get().getContent());
    } else {
      return ResponseEntity.status(HttpStatus.NOT_FOUND).body("File was not found");
    }
  }

  @RequestMapping(value = "/upload", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
  @ResponseBody
  public ResponseModel formUpload(HttpServletRequest request,
      @RequestParam("alias") String alias) {
    ResponseModel model = ResponseModel.getInstance();

    List<MultipartFile> fileList = new ArrayList<>();
    //创建一个通用的多部分解析器
//    CommonsMultipartResolver multipartResolver =
//        new CommonsMultipartResolver(request.getSession().getServletContext());
    //判断 request 是否有文件上传,即多部分请求
    if (multipartResolver.isMultipart(request)) {
      //转换成多部分request
      MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;
      //取得request中的所有文件名
      fileList = multiRequest.getFiles("file");
      if (fileList == null || fileList.size() <= 0) {
        model.setMessage("请传入文件");
        return model;
      }

      StringBuilder sqls = new StringBuilder();

      for (MultipartFile file : fileList) {
        try {
          if (file != null && !file.isEmpty()) {
            String fileMd5 = MD5Util.getMD5(file.getInputStream());
            FileDocument fileDocument = fileService.saveFile(alias, fileMd5, file);
            String fileName = file.getOriginalFilename();
            logger.info("文件名称：{}，文件ID：{}", fileName, fileDocument.getId());

            StringBuffer updateSql = new StringBuffer("update sp_article set file_id = '");
            updateSql.append(fileDocument.getId())
                .append("' where file_name = '")
                .append(fileName)
                .append("';");

            sqls.append(updateSql.toString());
            //spArticleService.updateFileIdByName(fileDocument.getId(), fileName);
          }
        } catch (Exception e) {
          logger.error("上传mongo异常", e);
        }
      }
      logger.info(sqls.toString());
    }

    model.setCode(ResponseModel.Success);
    model.setMessage("上传成功");
    return model;
  }

  /**
   * 表单上传文件 当数据库中存在该md5值时，可以实现秒传功能
   *
   * @param file 文件
   */
  @RequestMapping(value = "/upload1", method = RequestMethod.POST)
  @ResponseBody
  public ResponseModel formUpload1(@RequestParam("alias") String alias,
      @RequestParam("file") MultipartFile file) {
    ResponseModel model = ResponseModel.getInstance();
    try {
      if (file != null && !file.isEmpty()) {
        String fileMd5 = MD5Util.getMD5(file.getInputStream());
        FileDocument fileDocument = fileService.saveFile(alias, fileMd5, file);
        String fileName = file.getOriginalFilename();
        logger.info("文件名称：{}，文件ID：{}", fileName, fileDocument.getId());

        StringBuffer updateSql = new StringBuffer("update sp_article set file_id = '");
        updateSql.append(fileDocument.getId())
            .append("' where file_name = '")
            .append(fileName)
            .append("';");
        logger.info(updateSql.toString());

//        spArticleService.updateFileIdByName(fileDocument.getId(), fileName);

        model.setData(fileDocument.getId());
        model.setCode(ResponseModel.Success);
        model.setMessage("上传成功");
      } else {
        model.setMessage("请传入文件");
      }
    } catch (IOException | NoSuchAlgorithmException ex) {
      logger.error("上传mongo异常", ex);
      model.setMessage(ex.getMessage());
    }
    return model;
  }

  /**
   * 删除附件
   */
  @RequestMapping(value = "/delete/{id}", method = RequestMethod.GET)
  @ResponseBody
  public ResponseModel deleteFileByGetMethod(@RequestParam("alias") String alias,
      @PathVariable("id") String id) {
    ResponseModel model = ResponseModel.getInstance();
    if (!StringUtils.isEmpty(id)) {
      fileService.removeFile(alias, id, true);
      model.setCode(ResponseModel.Success);
      model.setMessage("删除成功");
    } else {
      model.setMessage("请传入文件id");
    }
    return model;
  }

  //将指定路径下的文件存储到mogodb的alias库中
  @RequestMapping(value = "/bthUpload", method = RequestMethod.POST)
  @ResponseBody
  public ResponseModel bthUpload(String path, String alias) {
//    String path = "D:\\spider\\pdf";
    ResponseModel model = ResponseModel.getInstance();
    List<MultipartFile> fileList = new ArrayList<>();
    StringBuilder sqls = new StringBuilder();

    File dir = new File(path);
    File[] files = dir.listFiles();
    for (File file : files) {
      if (file.isDirectory()) {
        continue;
      }

      try {
        FileInputStream inputStream = new FileInputStream(file);

        String contentType = "application/pdf";
        String name = file.getName();
        String suffix = name.split("\\.")[1];
        if (!"pdf".equals(suffix)) {
          contentType = "";
        }

        MultipartFile multipartFile = new MockMultipartFile(file.getName(), file.getName(),
            contentType, inputStream);

        fileList.add(multipartFile);
      } catch (FileNotFoundException e) {
        e.printStackTrace();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }

    for (MultipartFile file : fileList) {
      try {
        if (file != null && !file.isEmpty()) {
          String fileMd5 = MD5Util.getMD5(file.getInputStream());

          FileDocument fileDocument = fileService.saveFile(alias, fileMd5, file);
          String fileName = file.getOriginalFilename();
          logger.info("文件名称：" + fileName + "，文件ID：" + fileDocument.getId());

          StringBuffer updateSql = new StringBuffer("update sp_article set file_id = '");
          updateSql.append(fileDocument.getId())
              .append("' where file_name = '")
              .append(fileName)
              .append("';");

          sqls.append(updateSql.toString());

          jdbcTemplate.batchUpdate(updateSql.toString());
        }
      } catch (Exception e) {
        logger.error("上传mongo异常", e);
      }
    }
    logger.info(sqls.toString());

    model.setCode(ResponseModel.Success);
    model.setMessage("上传成功");
    return model;
  }

  //定时任务
  @RequestMapping(value = "/scheduleTask", method = RequestMethod.GET)
  @ResponseBody
  public ResponseModel scheduleTask() {
    ResponseModel responseModel = ResponseModel.getInstance();

    String path = "/data/spider/";
    String alias = "rand";

    //获取所有状态为0的数据  locate('.pdf',file_urls) != 0
    String sql = "select file_urls,file_img from sp_article where status = 0 and (locate('.pdf',file_urls) != 0 or file_img != '');";
    List<FileModel> fileModels = jdbcTemplate.query(sql, new FileMapperReader<FileModel>());

    List<List<FileModel>> partition = Lists.partition(fileModels, 30);
    partition.stream().forEach(list -> {
      doScheduleTask(list, path, alias);
    });

    responseModel.setCode(ResponseModel.Success);
    responseModel.setMessage("操作成功");
    return responseModel;
  }

  private void doScheduleTask(List<FileModel> fileModels, String path, String alias) {
    //遍历：
    //1. 下载图片+pdf
    //2. 文件上传至mongo，数据关联

    for (FileModel model : fileModels) {
      String fileUrls = model.getFileUrls();
      if(StringUtils.isBlank(fileUrls)) {
        continue;
      }

      String[] urls = fileUrls.split("\\,");
      for (String url : urls) {
        executorInstance.submit(() -> {
          String fileName = url.substring(url.lastIndexOf("/") + 1);
          if (!fileName.contains(".")) {
            //文件没有后缀，说明是非文件类型，跳过
            return;
          }

          File file = new File(path + fileName);
          if (file.isDirectory()) {
            return;
          }

          if (!file.exists()) {
            logger.info("[{}] >>首次下载文件url：[{}]", path, url);
            FileUtils.downloadFile(path, url, true);
            logger.info("[{}{}] >>文件下载成功", path, fileName);
          } else {
            logger.info("pdf文件 [{}{}] 已下载", path, fileName);
          }

          uploadFileAndLinkData(file, fileName, "0", alias, url);
        });
      }

      String fileImgUrl = model.getFileImg();
      if(StringUtils.isBlank(fileImgUrl)) {
        continue;
      }

      executorInstance.submit(() -> {
        String fileName = fileImgUrl.substring(fileImgUrl.lastIndexOf("/") + 1);
        if (!fileName.contains(".")) {
          //文件没有后缀，说明是非文件类型，跳过
          return;
        }

        File file = new File(path + fileName);
        if (file.isDirectory()) {
          return;
        }

        if (!file.exists()) {
          logger.info("[{}] >>首次下载文件url：[{}]", path, fileImgUrl);
          FileUtils.downloadFile(path, fileImgUrl, true);
          logger.info("[{}{}] >>文件下载成功", path, fileName);
        } else {
          logger.info("图片 [{}{}] 已下载", path, fileName);
        }

        uploadFileAndLinkData(file, fileName, "1", alias, fileImgUrl);
      });
    }

  }

  private void uploadFileAndLinkData(File file, String fileName, String type, String alias, String url) {
//    File file = new File(path + fileName);
    FileInputStream inputStream = null;
    try {
      inputStream = new FileInputStream(file);

      String contentType = "application/pdf";

      String suffix = fileName.split("\\.")[1];
      if ("jpg".equals(suffix) || "png".equals(suffix) || "jpeg".equals(suffix)) {
        contentType = "image/jpeg";
      }

      MultipartFile multipartFile = new MockMultipartFile(fileName, fileName, contentType, inputStream);

      //2. 文件上传至mongo，数据关联
      String fileMd5 = MD5Util.getMD5(multipartFile.getInputStream());

      logger.info("文件[{}]开始上传mongodb", fileName);
      FileDocument fileDocument = fileService.saveFile(alias, fileMd5, multipartFile);
      logger.info("文件：{} 上传成功 >> 文件ID：{}", fileName, fileDocument.getId());

      StringBuffer updateSql = new StringBuffer("update sp_article ");
      if ("0".equals(type)) {
        //pdf
        updateSql.append("set file_id = '")
            .append(fileDocument.getId())
            .append("', status=1 ")
            .append(" where file_name = '")
            .append(fileName)
            .append("';");
      } else if  ("1".equals(type)) {
        //图片
        updateSql.append("set img_id = '")
            .append(fileDocument.getId())
            .append("', status=1 ")
            .append(" where file_img = '")
            .append(url)
            .append("';");
      }

      logger.info("更新表数据，SQL：[{}]", updateSql.toString());
      jdbcTemplate.batchUpdate(updateSql.toString());
      logger.info("更新表数据>>成功");

    } catch (Exception e) {
      logger.error("IO异常", e);
    } finally {
      if (null != inputStream) {
        try {
          inputStream.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }

  class FileMapperReader<FileMode> implements RowMapper {

    @Override
    public Object mapRow(ResultSet resultSet, int i) throws SQLException {
      FileModel fileModel = new FileModel();
      fileModel.setFileImg(resultSet.getString("file_img"));
      fileModel.setFileUrls(resultSet.getString("file_urls"));
      return fileModel;
    }
  }

  class FileModel {

    String fileUrls;
    String fileImg;

    public String getFileUrls() {
      return fileUrls;
    }

    public void setFileUrls(String fileUrls) {
      this.fileUrls = fileUrls;
    }

    public String getFileImg() {
      return fileImg;
    }

    public void setFileImg(String fileImg) {
      this.fileImg = fileImg;
    }
  }


}